﻿namespace CodeSHARPer.ViewModel
{
    using Cinch;
    using CodeSHARPer.Services;
    using CodeSHARPer.ViewModel.Dialogs;
    using CodeSHARPer.ViewModel.Tools;
    using ICSharpCode.AvalonEdit.Document;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text.RegularExpressions;
    using System.Windows.Threading;

    partial class Workspace
    {
        private IEditor GetNextEditor(FindReplaceViewModel f, bool previous = false)
        {
            // There is no next open document if there is none or only one open
            if (this.Files.Count <= 1)
                return f.GetCurrentEditor();

            // There is no next open document If the user wants to search the current document only
            if (f.SearchIn == CodeSHARPer.ViewModel.Tools.SearchScope.CurrentDocument)
                return f.GetCurrentEditor();

            List<object> l = new List<object>(this.Files.Cast<object>());

            int idxStart = l.IndexOf(f.CurrentEditor);
            int i = idxStart;

            if (i >= 0)
            {
                Match m = null;

                bool textSearchSuccess = false;
                do
                {
                    if (previous == true)                  // Get next/previous document
                        i = (i < 1 ? l.Count - 1 : i - 1);
                    else
                        i = (i >= l.Count - 1 ? 0 : i + 1);

                    //// i = (i + (previous ? l.Count - 1 : +1)) % l.Count;

                    CodePadViewModel fTmp = l[i] as CodePadViewModel; // Search text in document
                    if (fTmp != null)
                    {
                        Regex r;
                        m = this.FindNextMatchInText(0, 0, false, fTmp.Text, ref f, out r);

                        textSearchSuccess = m.Success;
                    }
                }
                while (i != idxStart && textSearchSuccess != true);

                // Found a match so activate the corresponding document and select the text with scroll into view
                if (textSearchSuccess == true && m != null)
                {
                    this.ActiveDocument = l[i] as CodePadViewModel;

                    // Ensure that no pending calls are in the dispatcher queue
                    // This makes sure that we are blocked until bindings are re-established
                    // Bindings are required to scroll a selection into view
                    Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.SystemIdle, (Action)delegate
                    {
                        if (this.ActiveDocument != null)
                        {
                            this.ActiveDocument.TextEditorSelectionStart = m.Index;
                            this.ActiveDocument.TextEditorSelectionLength = m.Length;

                            // Reset cursor position to make sure we search a document from its beginning
                            this.ActiveDocument.TxtControl.SelectText(m.Index, m.Length);

                            f.CurrentEditor = l[i] as IEditor;

                            IEditor edi = f.GetCurrentEditor();

                            if (edi != null)
                                edi.Select(m.Index, m.Length);

                        }
                    });

                    return f.GetCurrentEditor();
                }
            }

            return null;
        }

        /// <summary>
        /// Find a match in a given peace of string
        /// </summary>
        Match FindNextMatchInText(int SelectionStart,             // CE.SelectionStart
                                  int SelectionLength,           // CE.SelectionLength
                                  bool InvertLeftRight,         // CE.Text
                                  string Text,                 // InvertLeftRight
                                  ref FindReplaceViewModel f,
                                  out Regex r)
        {
            if (InvertLeftRight)
            {
                f.SearchUp = !f.SearchUp;
                r = f.GetRegEx();
                f.SearchUp = !f.SearchUp;
            }
            else
                r = f.GetRegEx();

            return r.Match(Text, r.Options.HasFlag(RegexOptions.RightToLeft) ? SelectionStart : SelectionStart + SelectionLength);
        }

        private bool FindNext(FindReplaceViewModel f, bool InvertLeftRight = false)
        {
            IEditor editor = f.GetCurrentEditor();

            if (editor == null)
                return false;

            Regex r;
            Match m = this.FindNextMatchInText(editor.SelectionStart, editor.SelectionLength, InvertLeftRight, editor.Text, ref f, out r);

            if (m.Success)
            {
                editor.Select(m.Index, m.Length);

                return true;
            }
            else
            {
                if (f.SearchIn == CodeSHARPer.ViewModel.Tools.SearchScope.CurrentDocument)
                {
                    ServiceResolver.Instance.MessageBoxService.ShowInformation("There are no more search results to display.");

                    return false;
                }

                // we have reached the end of the document
                // start again from the beginning/end,
                object oldEditor = f.CurrentEditor;
                do
                {
                    if (f.SearchIn == CodeSHARPer.ViewModel.Tools.SearchScope.AllDocuments)
                    {
                        editor = this.GetNextEditor(f, r.Options.HasFlag(RegexOptions.RightToLeft));

                        if (editor == null)
                            return false;

                        f.CurrentEditor = editor;

                        return true;
                    }

                    if (r.Options.HasFlag(RegexOptions.RightToLeft))
                        m = r.Match(editor.Text, editor.Text.Length - 1);
                    else
                        m = r.Match(editor.Text, 0);

                    if (m.Success)
                    {
                        editor.Select(m.Index, m.Length);
                        break;
                    }
                    else
                        ServiceResolver.Instance.MessageBoxService.ShowInformation("No further occurence found.", "Search");

                } while (f.CurrentEditor != oldEditor);
            }

            return false;
        }

        /// <summary>
        /// Gets the current line in which the cursor is currently located
        /// </summary>
        private static int GetCurrentEditorLine(CodePadViewModel f)
        {
            int iCurrLine = 0;

            try
            {
                int start, length;
                bool IsRectangularSelection = false;

                f.TxtControl.CurrentSelection(out start, out length, out IsRectangularSelection);

                iCurrLine = f.Document.GetLineByOffset(start).LineNumber;
            }
            catch { }
            return iCurrLine;
        }

        private void ShowGotoLineDialog()
        {
            CodePadViewModel f = this.ActiveDocument as CodePadViewModel;

            if (f != null)
            {
                CodeSHARPer.ViewModel.Dialogs.GotoLineViewModel dlgVM = null;

                try
                {
                    int iCurrLine = Workspace.GetCurrentEditorLine(f);

                    //inits the view model
                    dlgVM = new CodeSHARPer.ViewModel.Dialogs.GotoLineViewModel(1, f.Document.LineCount, iCurrLine);

                    bool r = ServiceResolver.Instance.UIVisualizerService.ShowDialog("GotoLineView", dlgVM).GetValueOrDefault();
                    if (r)
                    {
                        DocumentLine line = f.Document.GetLineByNumber(dlgVM.LineNumber);

                        f.TxtControl.SelectText(line.Offset, 0);      // Select text with length 0 and scroll to where
                        f.TxtControl.ScrollToLine(dlgVM.LineNumber); // we are supposed to be at
                    }
                }
                catch (Exception exc)
                {
                    ServiceResolver.Instance.MessageBoxService.ShowError(exc.Message, "An unexpected error occured.");
                }
            }
        }

        private void ShowFindReplaceToolWindow(bool ShowFind = true)
        {
            CodePadViewModel f = this.ActiveDocument as CodePadViewModel;

            if (f != null)
            {
                try
                {
                    SetFindNextAction();

                    // determine whether Find or Find/Replace is to be executed
                    this.FindAndReplace.ShowAsFind = ShowFind;

                    if (f.TxtControl != null)      // Search by default for currently selected text (if any)
                    {
                        string textToFind;
                        f.TxtControl.GetSelectedText(out textToFind);

                        if (!string.IsNullOrEmpty(textToFind))
                            this.FindAndReplace.TextToFind = textToFind;
                    }

                    SetFindAndReplaceEditor(f);

                    //add the find and replace tool to the tools collection
                    if (this.Tools.OfType<FindReplaceViewModel>().Count() == 0)
                        this.Tools.Add(this.FindAndReplace);

                    //make sure that the tool is focus
                    this.FindAndReplace.IsVisible = true;
                    this.FindAndReplace.IsSelected = true;
                    this.FindAndReplace.IsActive = true;
                }
                catch (Exception exc)
                {
                    ServiceResolver.Instance.MessageBoxService.ShowError(exc.Message, "An unexpected error occured.");
                }
            }
        }

        private void SetFindAndReplaceEditor(CodePadViewModel f)
        {
            this.FindAndReplace.CurrentEditor = f;
        }

        private void SetFindNextAction()
        {
            if (this.FindAndReplace.FindNext == null)
                this.FindAndReplace.FindNext = this.FindNext;
        }
    }
}
